#include "NahidaBenchmarkTest.h"

NahidaProject::NahidaBenchmarkTest::RuntimeResult NahidaProject::NahidaBenchmarkTest::Time(int count, std::vector<NahidaProject::NahidaBenchmarkTest::Test> func) {
	NahidaProject::NahidaBenchmarkTest::RuntimeResult runtimes;
	for (const auto& e : func) {
		NahidaProject::NahidaBenchmarkTest::BenchmarkTestResult benchmarkTestResult;
		benchmarkTestResult.name = e.name;
		benchmarkTestResult.minimalRecordedRuntime = std::numeric_limits<double>::max();
		benchmarkTestResult.maximalRecordedRuntime = 0;
		benchmarkTestResult.averageRuntime = 0;

		double old_avg = 0, old_var = 0;

		std::chrono::high_resolution_clock::duration total = std::chrono::high_resolution_clock::duration::zero();
		for (int i = 0; i < count; ++i) {
			std::chrono::high_resolution_clock::time_point t = std::chrono::high_resolution_clock::now();
			e.func();
			std::chrono::high_resolution_clock::duration d = std::chrono::high_resolution_clock::now() - t;
			total += d;
			long long duration = std::chrono::duration_cast<std::chrono::nanoseconds>(d).count();
			benchmarkTestResult.averageRuntime += duration;

			if (duration < benchmarkTestResult.minimalRecordedRuntime) {
				benchmarkTestResult.minimalRecordedRuntime = duration;
			}

			if (duration > benchmarkTestResult.maximalRecordedRuntime) {
				benchmarkTestResult.maximalRecordedRuntime = duration;
			}

			benchmarkTestResult.distributionOfRuntimes[duration]++;
			if (i == 0) {
				old_avg = benchmarkTestResult.averageRuntime = duration;
			}
			else {
				benchmarkTestResult.averageRuntime = old_avg + (duration - old_avg) / (i + 1);
				benchmarkTestResult.runtimeVariance = old_var + (duration - old_avg) * (duration - benchmarkTestResult.averageRuntime);
				old_avg = benchmarkTestResult.averageRuntime;
				old_var = benchmarkTestResult.runtimeVariance;
			}
		}
		benchmarkTestResult.runtimeVariance = count > 1 ? benchmarkTestResult.runtimeVariance / (count - 1) : 0;
		benchmarkTestResult.standardDeviation = sqrt(benchmarkTestResult.runtimeVariance);
		runtimes.insert(make_pair(total, benchmarkTestResult));
	}
	return runtimes;
}

NahidaProject::NahidaBenchmarkTest::CompareResult NahidaProject::NahidaBenchmarkTest::Compare(const RuntimeResult& runtimes) {
	CompareResult compare;
	for (const auto& a : runtimes) {
		Comparison comp;
		comp.name = a.second.name;
		for (const auto& b : runtimes) {
			if (a.second.name == b.second.name) {
				comp.pct.emplace_back(0);
				continue;
			}
			double d0 = std::chrono::duration_cast<std::chrono::nanoseconds>(a.first).count();
			double d1 = std::chrono::duration_cast<std::chrono::nanoseconds>(b.first).count();
			comp.pct.emplace_back((d1 - d0) / d1 * 100.0);
		}
		compare.insert(std::make_pair(a.first, comp));
	}
	return compare;
}
